<?php

/**
 * Session支持
 *
 * Sparrow提供的session支持，支持三种存储Session的方式
 * <ol>
 *  <li>文件级Session --- 可以自定义Session存储路径</li>
 *  <li>数据库Session --- 将Session存入数据库，通常在单数据库应用中用于多台Web共享Session</li>
 *  <li>基于Memcache的Session --- 将Session存入Memcached，在大规模应用（特别是具有数据库集群）中用于Session共享</li>
 * </ol>
 *
 */


 

/**
 * 获取session实例
 *
 * 通过此方法可获取session实例，获取后即可开启或操作session
 * 相关配置
 * <code>
 * // 使用文件存储session
 * session.type    -filebased
 * session.path
 *
 * // 使用数据库存储session
 * session.type    -database
 * session.db
 * session.table
 *
 * // 使用memcache存储session
 * session.type    -memcached
 * session.servers
 * </code>
 * <br />用例<br />
 * <code>
 * $session = session();
 * $session->start();
 * // 像往常一样操作session
 * $_SESSION['test'] = 'test';
 * </code>
 *
 */

define ('SESSION_MAX_LIFE_TIME',1800);
//define("SESSION_TYPE",'database'); // database memcached filebased
//database
define ('SESSION_DB',1800);
define ('SESSION_TABLE',1800);
//end database
//memcached
define('SESSION_SERVER', '');
define('SESSION_PATH', '');
//end memcached
define ('SESSION_MAX_LIFE_TIME',1800);
define ('SESSION_MAX_LIFE_TIME',1800);
define ('SESSION_MAX_LIFE_TIME',1800);
define ('SESSION_MAX_LIFE_TIME',1800);




function session()
{
    static $session = null;
    if ($session === null)
    {
        $type = SESSION_TYPE;//config here!!!
        $max_life_time =SESSION_MAX_LIFE_TIME ? SESSION_MAX_LIFE_TIME : 1800;
        $session_class = ucfirst($type) . 'Session';
        $session = BaseSession::create($session_class);
        $session->setMaxLifeTime($max_life_time); 
        switch ($type)
        {
            case 'database':
                $get_db_connection = SESSION_DB;
                $table_name = SESSION_TABLE;
                $db = $get_db_connection();
                $session->setDatabaseConnection($db);
                $session->setTable($table_name);
                break;
            case 'memcached':
                $session->addServers(SESSION_SERVER);
                break;
            // 默认filebased
            default:
                $session->setSavePath(SESSION_PATH);
                break;
        }
    }

    return $session;
}


/**
 * 基础session支持类
 *
 * session基础类，有几点
 * <ol>
 * <li>除了过期时间外，完全使用PHP内置Session的各项功能(session_id生成，指定保存路径等.)</li>
 * <li>ID使用PHP内置ID，要获取当前session_id，通过session_id()函数</li>
 * </ol>
 *
 */
class BaseSession 
{
    /**
     * 当前Session实例
     */
    public static $session = null;

    /**
     * Session过期时间
     */
    protected $_max_life_time = 1800;

    /**
     * 获取Session实例
     *
     * 由于在一次请求中不能存在两个Session，因此采用单例模式，通过该方法获取Session实例
     *
     * @author Garbin
     * @access public
     * @param  string $session_class Session类名
     * @return Object 返回Session实例
     **/
    public function create($session_class)
    {
        if (self::$session === null)
        {
            self::$session = new $session_class();
        }

        return self::$session;
    }

    /**
     * 启动session
     *
     * 调用该方法开启Session，开启成功后，您就可以直接像往常一样操作$_SESSION变量了。
     *
     * @author Garbin
     * @access public
     * @return void
     **/
    public function start()
    {
        session_set_save_handler(array(&$this, 'open'),
                                 array(&$this, 'close'),
                                 array(&$this, 'read'),
                                 array(&$this, 'write'),
                                 array(&$this, 'destroy_handler'),
                                 array(&$this, 'gc'));
        session_start();
        $this->_now= time();
    }

    /**
     * 设置过期时间
     *
     * 设置Session的过期时间，以秒为单位
     *
     * @author    Garbin
     * @access public
     * @param  int $time    过期时间(如：1800表示半个小时)
     * @return void
     */
    public function setMaxLifeTime($time)
    {
        $this->_max_life_time = intval($time);
    }

    /**
     * 销毁Session
     *
     * 销毁当前Session
     *
     * @author Garbin
     * @access public
     * @return void
     **/
    public function destroy()
    {
        session_destroy();
    }

    /**
     * 系统回调函数Open
     *
     * session_set_save_handler的回调函数open，关于更多信息参看php手册
     *
     * @author    Garbin
     * @param     string $save_path session保存路径
     * @param     string $sess_name session名
     * @return    bool
     */
    public function open($save_path, $sess_name)
    {
        return true;
    }


    /**
     * 系统回调函数close
     *
     * session_set_save_handler的回调函数close，关于更多信息参看php手册
     *
     * @author    Garbin
     * @return    bool
     */
    public function close()
    {

        return true;
    }

    /**
     * 系统回调函数read
     *
     * session_set_save_handler的回调函数read，关于更多信息参看php手册
     *
     * @author    Garbin
     * @param     string $id session_id
     * @return    string 返回保存的session数据
     */
    public function read($id)
    {
        return '';
    }

    /**
     * 系统回调函数write
     *
     * session_set_save_handler的回调函数write，关于更多信息参看php手册
     *
     * @author    Garbin
     * @param     string $id session_id
     * @param     string $data session数据
     * @return    bool
     */
    public function write($id, $data)
    {
        return true;
    }

    /**
     * 系统回调函数destroy，销毁session时调用
     *
     * session_set_save_handler的回调函数destroy，关于更多信息参看php手册
     *
     * @author    Garbin
     * @param     string $id session_id
     * @return    bool
     */
    public function destroy_handler($id)
    {
        return true;
    }


    /**
     * 系统回调函数gc，垃圾回收
     *
     * session_set_save_handler的回调函数gc，关于更多信息参看php手册
     *
     * @author    Garbin
     * @param     int $max_life_time 系统设置的session过期时间，在这里不使用它，使用自定义的过期时间
     * @return    bool
     */
    public function gc($max_life_time)
    {
        return true;
    }
}

/**
 * 基于文件的session
 *
 * @author Garbin
 **/
class FilebasedSession extends BaseSession
{
    /**
     * 设置session保存路径
     *
     * @author Garbin
     * @access public
     * @param  string $path
     * @return void
     **/
    public function setSavePath($path)
    {
        session_save_path($path);
    }

    protected function _getFilePath($id)
    {
        return session_save_path() . 'sess_' . $id;
    }

    public function gc($max_life_time)
    {
        $session_list = glob(session_save_path() . 'sess_*');
        foreach ($session_list as $session_file)
        {
            if (filemtime($session_file) + $this->_max_life_time < $this->_now)
            {
                unlink($session_file);
            }
        }

        return true;
    }

    public function read($id)
    {
        $data = '';
        $file = $this->_getFilePath($id);
        if (is_file($file))
        {
            $data = file_get_contents($file);
        }

        return $data;
    }

    public function write($id, $data)
    {
        $file = $this->_getFilePath($id);
        $r = file_put_contents($file, $data, LOCK_EX);
        if ($r === false)
        {
            // 猜测：因为该函数是系统在session关闭前回调的，因此系统无法捕捉到抛出的位置
            throw new Exception("The session save path is unwritable :{$file}");
        }

        return $r;
    }

    public function destroy_handler($id)
    {
        $file = $this->_getFilePath($id);
        unlink($file);
    }
}

/**
 * 基于数据库的session，使用该类需要创建指定的数据库表
 *
 * @author Garbin
 **/
/*
// 数据库表创建语句
CREATE TABLE `sparrow_session` (
  `id` char(32) NOT NULL,
  `expiry` int(10) unsigned NOT NULL,
  `data` text NOT NULL,
  PRIMARY KEY (`id`),
  KEY `expiry` (`expiry`)
) TYPE=MyISAM;
*/

class DatabaseSession extends BaseSession
{
    public $db = null;
    protected $_table = '';

    /**
     * 设置数据库连接
     *
     * @author Garbin
     * @access public
     * @param  object $db
     * @return void
     **/
    public function setDatabaseConnection(IDataObject $db)
    {
        $this->db = $db;
    }

    /**
     * 指定存储表
     *
     * @author Garbin
     * @access public
     * @param  string $table_name
     * @return void
     **/
    public function setTable($table_name)
    {
        $this->_table = $table_name;
    }

    // TODO:测试
    public function gc($max_life_time)
    {
    	//TODO here!!!
        $sql = "DELETE FROM {$this->_table} WHERE expiry + {$this->_max_life_time} < {$this->_now}";

        $this->db->exec($sql);

        return true;
    }

    public function read($id)
    {
        $sql = "SELECT data FROM {$this->_table} WHERE id = '{$id}'";
        $data = $this->db->fetchOne($sql);
        $return = '';
        if (!empty($data))
        {
        	//TODO 移除反斜杠
            $return = $data['data'];
        }

        return $return;
    }

    public function write($id, $data)
    {
        /* 若不进行此处理将导致数据中有单引号后会错误 */
        $data = addslashes($data);

        /* 插入Session数据 */
        $expiry = $this->_now + $this->_max_life_time;
        $sql = "REPLACE INTO {$this->_table}(id, expiry, data) VALUES('{$id}', {$expiry}, '{$data}')";
        $this->db->exec($sql);

        return true;
    }

    public function destroy_handler($id)
    {
        $sql = "DELETE FROM {$this->_table} WHERE id='{$id}'";

        return $this->db->exec($sql);
    }
}

/**
 * 基于Memcached的session
 **/
class MemcachedSession extends BaseSession
{
    protected $_memcached = null;

    /**
     * 添加memcached server
     *
     * @author Garbin
     * @param  array $server array(array('10.202.1.109', 12321))
     * @return void
     **/
    public function addServers($server)
    {
        if ($this->_memcached === null)
        {
            $this->_memcached = new Memcached();
        }
        $this->_memcached->addServers($server);
    }

    public function read($id)
    {
        $data = $this->_memcached->get($id);
        $return = '';
        if ($data !== false)
        {
            $return = $data;
        }

        return $return;
    }

    public function write($id, $data)
    {
        $this->_memcached->set($id, $data, $this->_max_life_time);

        return true;
    }

    public function destroy_handler($id)
    {
        return $this->_memcached->delete($id);
    }
}
